"""
:synopsis: Calculate/Convert/Fix Field(s)
   Analysis Options: Calculate `FullName` and/or `LgcyFulSt`, Calculate `FullAddr`and/or `LgcyFulAdd`, Calculate `Parity`, Calculate Null Values to `N`, Calculate `RCLMATCH`/`RCLSIDE`, Calculate `MSAGComm`, Convert `Type` (`LEGACY` or `NEXT GEN`), Convert `Direction` (`LEGACY` or `NEXT GEN`), Fix Leading/Trailing Spaces, Fix Domain Case, Fix Level Fields
:authors: Riley Baird (OK), Emma Baker (OK)
"""


# tool import
from ...lib.session import config
from ...lib.datachanges import CalculateConvertFixImplementer
# system import
import arcpy
import random
try:
    from typing import ClassVar, Optional, Union, Protocol, TypeVar, Generic, TypeVar, overload, get_args, Literal
except:
    pass


random_start_phrase = random.choice(['Make it so!', 'Hello World!', 'As the prophecy foretold...', 'Greetings earthlings.', 'All are bases are belong to us.', 'The Jig is up!'])

tool_switch: str = "CALCULATE_CONVERT_FIX"
error_list = ["PLEASE SELECT A STANDARD GDB", "NO FCS FOUND IN SELECTED GDB", "NO FIELDS FOUND IN SELECTED FC", "--PLEASE SELECT FC--", "--PLEASE SELECT FIELD--"]

## Custom Tool Parameter Information
class CustomToolParameterInfo:
    def __init__(self):
        ## Parameter Indexes
        self.std_gdb_idx = 0
        self.selected_analysis_idx = self.std_gdb_idx + 1
        self.fullname_fc_name_idx = self.selected_analysis_idx + 1
        self.fullname_variant_idx = self.fullname_fc_name_idx + 1
        self.fulladdr_variant_idx = self.fullname_variant_idx + 1
        self.parity_placeholder_idx = self.fulladdr_variant_idx + 1
        self.submit_fc_name_idx = self.parity_placeholder_idx + 1
        self.rclmatch_rclside_placeholder_idx = self.submit_fc_name_idx + 1
        self.msagcomm_fc_name_idx = self.rclmatch_rclside_placeholder_idx + 1
        self.type_fc_name_idx = self.msagcomm_fc_name_idx + 1
        self.type_variant_idx = self.type_fc_name_idx + 1
        self.direction_fc_name_idx = self.type_variant_idx + 1
        self.direction_variant_idx = self.direction_fc_name_idx + 1
        self.spaces_fc_name_idx = self.direction_variant_idx + 1
        self.domain_fc_name_idx = self.spaces_fc_name_idx + 1
        self.level_fc_name_idx = self.domain_fc_name_idx + 1
        ## Std Dataset Names
        self.required_dataset_name = config.gdb_info.required_dataset_name  # "NG911"
        self.optional_dataset_name = config.gdb_info.optional_dataset_name  # "OptionalLayers"
        ## Std FCs
        self.required_fc_name_list = list(config.required_feature_class_names)
        self.road_fc = config.feature_classes.road_centerline
        self.address_fc = config.feature_classes.address_point
        ## Std Fields
        # Target Analysis Fields
        self.fullname_field = config.fields.fullname
        self.lgcyfullname_field = config.fields.lgcyfulst
        self.fulladdr_field = config.fields.fulladdr
        self.lgcyfulladdr_field = config.fields.lgcyfuladd
        self.parity_l_field = config.fields.parity_l
        self.parity_r_field = config.fields.parity_r
        self.submit_field = config.fields.submit
        self.rclmatch_field = config.fields.rclmatch
        self.rclside_field = config.fields.rclside
        self.msagcomm_field = config.fields.msagcomm
        self.msagcomm_l_field = config.fields.msagcomm_l
        self.msagcomm_r_field = config.fields.msagcomm_r
        self.geomsag_l_field = config.fields.geomsag_l
        self.geomsag_r_field = config.fields.geomsag_r
        self.from_level_field = config.fields.fromlevel
        self.to_level_field = config.fields.tolevel
        # Analysis Type Dictionary
        self.analysis_dict = {
            f"None": f"--PLEASE SELECT ANALYSIS--",
            f"{self.fullname_field.role}": f"Calculate `{self.fullname_field.name}` and/or `{self.lgcyfullname_field.name}`",
            f"{self.fulladdr_field.role}": f"Calculate `{self.fulladdr_field.name}` and/or `{self.lgcyfulladdr_field.name}`",
            f"parity": f"Calculate `{self.parity_l_field.name}` and `{self.parity_r_field.name}`",
            f"nulls": f"Calculate Null Values to 'N'",
            f"{self.rclmatch_field.role}": f"Calculate `{self.rclmatch_field.name}` and `{self.rclside_field.name}`",
            f"{self.msagcomm_field.role}": f"Calculate `{self.msagcomm_field.name}`",
            f"type": "Convert `Type` (`LEGACY` or `NEXT GEN`)",
            f"direction": "Convert `Direction` (`LEGACY` or `NEXT GEN`)",
            f"spaces": "Fix Leading/Trailing Spaces",
            f"domain": "Fix Domain Case",
            f"level": "Fix Level Fields"
        }
        self.analysis_string = '\n'.join([f'\t- {analysis}' for analysis in list(self.analysis_dict.values())[1:]])
        self.variant_list = ["NEXT-GEN", "LEGACY"]
custom_param = CustomToolParameterInfo


class CalculateConvertFix:
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = f"Calculate/Convert/Fix Field(s)"
        self.description = f"Calculate/Convert/Fix Field(s)\n\nAnalysis Options:\n{custom_param().analysis_string}"
        self.canRunInBackground = False
        self.category = "3 - Enhancement"

    def getParameterInfo(self):
        """Define parameter definitions"""

        params = []

        std_gdb = arcpy.Parameter(
            displayName=f"Select standard geodatabase.\n - Requires standard schema.",
            name="std_gdb",
            datatype="DEWorkspace",
            parameterType="Required",
            direction="Input")
        params += [std_gdb]

        selected_analysis = arcpy.Parameter(
            displayName=f"\nSelect desired analysis.",
            name="selected_analysis",
            datatype="GPString",
            parameterType="Required",
            direction="Input")
        selected_analysis.filter.type = "ValueList"
        selected_analysis.filter.list = list(custom_param().analysis_dict.values())
        selected_analysis.value = list(custom_param().analysis_dict.values())[0]
        params += [selected_analysis]

        fullname_fc_name_parameter = arcpy.Parameter(
            displayName=f"\nSelect standard feature class for {custom_param().analysis_dict[f'{custom_param().fullname_field.role}']} analysis.",
            name="fullname_fc_name_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        fullname_fc_name_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        fullname_fc_name_parameter.filter.type = "ValueList"
        fullname_fc_name_parameter.filter.list = [custom_param().address_fc.name, custom_param().road_fc.name]
        params += [fullname_fc_name_parameter]

        fullname_variant_parameter = arcpy.Parameter(
            displayName=f"\nSelect variant.",
            name="fullname_variant_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        fullname_variant_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        fullname_variant_parameter.filter.type = "ValueList"
        fullname_variant_parameter.filter.list = custom_param().variant_list
        params += [fullname_variant_parameter]

        fulladdr_variant_parameter = arcpy.Parameter(
            displayName=f"\nSelect variant for {custom_param().analysis_dict[f'{custom_param().fulladdr_field.role}']} analysis.\n - Requires `{custom_param().address_fc.name}`",
            name="fulladdr_variant_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        fulladdr_variant_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        fulladdr_variant_parameter.filter.type = "ValueList"
        fulladdr_variant_parameter.filter.list = custom_param().variant_list
        params += [fulladdr_variant_parameter]

        parity_placeholder_parameter = arcpy.Parameter(
            displayName=f"\nStandard feature class(es) for {custom_param().analysis_dict[f'parity']} analysis:",
            name="parity_placeholder_parameter",
            datatype="GPValueTable",
            parameterType="Optional",
            direction="Input")
        parity_placeholder_parameter.columns = [['GPString', f'Standard Feature Class(es)', 'ReadOnly']]
        parity_placeholder_parameter.filters[0].type = "ValueList"
        parity_placeholder_parameter.filters[0].list = [f'{custom_param().road_fc.name}']
        parameter_value_list = [f'{custom_param().road_fc.name}']
        parity_placeholder_parameter.values = [parameter_value_list]
        params += [parity_placeholder_parameter]

        nulls_fc_name_parameter = arcpy.Parameter(
            displayName=f"\nSelect standard feature class for {custom_param().analysis_dict[f'nulls']} analysis.\n\t- Tool will calculate all fields with {config.domains.YESNO.name} domain.",
            name="nulls_fc_name_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        nulls_fc_name_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        nulls_fc_name_parameter.filter.type = "ValueList"
        nulls_fc_name_parameter.filter.list = custom_param().required_fc_name_list
        params += [nulls_fc_name_parameter]

        rclmatch_rclside_placeholder_parameter = arcpy.Parameter(
            displayName=f"\nStandard feature class(es) for {custom_param().analysis_dict[f'{custom_param().rclmatch_field.role}']} analysis:",
            name="rclmatch_rclside_placeholder_parameter",
            datatype="GPValueTable",
            parameterType="Optional",
            direction="Input")
        rclmatch_rclside_placeholder_parameter.columns = [['GPString', f'Standard Feature Class(es)', 'ReadOnly']]
        rclmatch_rclside_placeholder_parameter.filters[0].type = "ValueList"
        rclmatch_rclside_placeholder_parameter.filters[0].list = [f'{custom_param().road_fc.name}\n{custom_param().address_fc.name}']
        parameter_value_list = [f'{custom_param().road_fc.name}\n{custom_param().address_fc.name}']
        rclmatch_rclside_placeholder_parameter.values = [parameter_value_list]
        params += [rclmatch_rclside_placeholder_parameter]

        msagcomm_fc_name_parameter = arcpy.Parameter(
            displayName=f"\nSelect standard feature class for {custom_param().analysis_dict[f'type']} analysis.",
            name="msagcomm_fc_name_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        msagcomm_fc_name_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        msagcomm_fc_name_parameter.filter.type = "ValueList"
        msagcomm_fc_name_parameter.filter.list = [custom_param().address_fc.name, custom_param().road_fc.name]
        params += [msagcomm_fc_name_parameter]

        type_fc_name_parameter = arcpy.Parameter(
            displayName=f"\nSelect standard feature class for {custom_param().analysis_dict[f'type']} analysis.",
            name="type_fc_name_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        type_fc_name_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        type_fc_name_parameter.filter.type = "ValueList"
        type_fc_name_parameter.filter.list = [custom_param().address_fc.name, custom_param().road_fc.name]
        params += [type_fc_name_parameter]

        type_variant_parameter = arcpy.Parameter(
            displayName=f"\nSelect variant.\n\t- Calculate to user-specified from the opposite option.",
            name="type_variant_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input")
        type_variant_parameter.filter.type = "ValueList"
        type_variant_parameter.filter.list = custom_param().variant_list
        type_variant_parameter.value = custom_param().variant_list[0]
        params += [type_variant_parameter]

        direction_fc_name_parameter = arcpy.Parameter(
            displayName=f"\nSelect standard feature class for {custom_param().analysis_dict[f'direction']} analysis.",
            name="direction_fc_name_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        direction_fc_name_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        direction_fc_name_parameter.filter.type = "ValueList"
        direction_fc_name_parameter.filter.list = [custom_param().address_fc.name, custom_param().road_fc.name]
        params += [direction_fc_name_parameter]

        direction_variant_parameter = arcpy.Parameter(
            displayName=f"\nSelect variant.\n\t- Calculate to user-specified from the opposite option.",
            name="direction_variant_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input")
        direction_variant_parameter.filter.type = "ValueList"
        direction_variant_parameter.filter.list = custom_param().variant_list
        direction_variant_parameter.value = custom_param().variant_list[0]
        params += [direction_variant_parameter]

        spaces_fc_name_parameter = arcpy.Parameter(
            displayName=f"\nSelect standard feature class for {custom_param().analysis_dict[f'spaces']} analysis.\n - Tool will remove leading and trailing spaces from all `TEXT` fields in selected feature class.",
            name="spaces_fc_name_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        spaces_fc_name_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        spaces_fc_name_parameter.filter.type = "ValueList"
        spaces_fc_name_parameter.filter.list = custom_param().required_fc_name_list
        params += [spaces_fc_name_parameter]

        domain_fc_name_parameter = arcpy.Parameter(
            displayName=f"\nSelect standard feature class for {custom_param().analysis_dict[f'domain']} analysis.\n - Tool will correct character casing for all fields that have Domains.",
            name="domain_fc_name_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        domain_fc_name_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        domain_fc_name_parameter.filter.type = "ValueList"
        domain_fc_name_parameter.filter.list = custom_param().required_fc_name_list
        params += [domain_fc_name_parameter]

        level_field_parameter = arcpy.Parameter(
            displayName=f"\nSelect level field for {custom_param().analysis_dict[f'level']} analysis.\n - Requires `{CustomToolParameterInfo().road_fc.name}`",
            name="level_field_parameter",
            datatype="GPString",
            parameterType="Optional",
            direction="Input",
            multiValue=True)
        level_field_parameter.controlCLSID = "{38C34610-C7F7-11D5-A693-0008C711C8C1}"
        level_field_parameter.filter.type = "ValueList"
        level_field_parameter.filter.list = [CustomToolParameterInfo().from_level_field.name, CustomToolParameterInfo().to_level_field.name]
        params += [level_field_parameter]

        return params

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True

    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""

        std_gdb = parameters[custom_param().std_gdb_idx]
        selected_analysis = parameters[custom_param().selected_analysis_idx]
        std_gdb.value = std_gdb.value
        if selected_analysis.value != list(custom_param().analysis_dict.values())[0] and selected_analysis.altered and not selected_analysis.hasBeenValidated:
            selected_analysis.value = selected_analysis.value
            key_idx = list(custom_param().analysis_dict.values()).index(selected_analysis.value)
            analysis_type = list(custom_param().analysis_dict.keys())[key_idx]
            analysis_indexes = [idx for idx in range(custom_param().selected_analysis_idx + 1, len(parameters)) if analysis_type in parameters[idx].name]
            for analysis_idx in analysis_indexes:
                if 'placeholder' in parameters[analysis_idx].name:
                    pass
                elif parameters[analysis_idx].datatype in ["Value Table", "GPValueTable"]:
                    parameters[analysis_idx].values = parameters[analysis_idx].values
                else:
                    parameters[analysis_idx].value = parameters[analysis_idx].value
                parameters[analysis_idx].enabled = True
            for idx in range(custom_param().selected_analysis_idx + 1, len(parameters)):
                if idx not in analysis_indexes:
                    parameters[idx].enabled = False
        elif selected_analysis.value != list(custom_param().analysis_dict.values())[0] and selected_analysis.hasBeenValidated:
            selected_analysis.value = selected_analysis.value
            for idx in range(custom_param().selected_analysis_idx + 1, len(parameters)):
                if 'placeholder' in parameters[idx].name:
                    pass
                elif parameters[idx].datatype in ["Value Table", "GPValueTable"]:
                    parameters[idx].values = parameters[idx].values
                else:
                    parameters[idx].value = parameters[idx].value
                parameters[idx].enabled = parameters[idx].enabled
        else:
            if std_gdb.value:
                std_gdb.value = std_gdb.value
            if selected_analysis.value:
                selected_analysis.value = selected_analysis.value
            for idx in range(custom_param().selected_analysis_idx + 1, len(parameters)):
                if parameters[idx].datatype in ["Value Table", "GPValueTable"]:
                    parameters[idx].values = parameters[idx].values
                else:
                    parameters[idx].value = parameters[idx].value
                parameters[idx].enabled = False

        return

    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""
        ## table parameter
        for idx in range(custom_param().selected_analysis_idx + 1, len(parameters)):
            if parameters[idx].hasBeenValidated and parameters[idx].enabled and parameters[idx].controlCLSID == "{38C34610-C7F7-11D5-A693-0008C711C8C1}" and not parameters[idx].value:
                parameters[idx].setErrorMessage(f"PLEASE SELECT AT LEAST ONE OPTION.")

        return

    def execute(self, parameters, messages):
        """The source code of the tool."""
        ## Process parameters from user-specified values

        arcpy.AddMessage(f"{random_start_phrase}")
        arcpy.AddMessage(f"\nCalculate/Convert/Fix...")
        current_tool_execute = CalculateConvertFixImplementer(parameters, custom_param().analysis_dict, custom_param().variant_list)
        arcpy.AddMessage(f"...User-specified Analysis: {current_tool_execute.selected_analysis}...\n\n")
        current_tool_execute.implement_analysis()


if __name__ == "__main__":
    raise Exception("This module is a dependency of an ArcGIS Python Toolbox and should not be executed directly.")